#!/usr/bin/env python
# coding: utf-8

# In[8]:


#数据分析与预处理说明
#1：特征类别名称
#status of existing checking account 现有支票帐户状况
#duration in month 还了多久贷款了/债务时间
# credit history 信贷历史
# purpose 贷款目的
# credit amount 贷款剩余金额
# savings account and bonds 存款账户 18.3%
# present employment since 截止到现在的就职年限
# installment rate in percentage of disposable income 分期付款占可支配收入的百分比
# personal status and sex 性别
# other debtors or guarantors 担保人  90.7% 
# a present residence since 居住地
# property 资产    15.4%
# age in years 贷款时的年纪
# other installment plans 分期付款计划   81.4%
# housing 无房/租房/自有房
# number of existing credits at this bank 已有的银行卡数量
# job 工作
# number of people being liable to provide maintenance for 可还款人数
# telephone 电话       59.6%
# foreign worker 是否为外籍员工
# creditability 信用好坏


# In[20]:


# 模块一：库导入与配置
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
from matplotlib import rcParams
from matplotlib.ticker import PercentFormatter

# 全局配置
rcParams['font.sans-serif'] = ['SimHei']  # 中文字体设置
plt.rcParams['axes.unicode_minus'] = False  # 负号显示修正
COLOR_MISSING = '#ff7f0e'  # 缺失值颜色
COLOR_NORMAL = '#1f77b4'   # 正常值颜色



# In[21]:


# 模块二：数据配置
# 连续变量配置（基于0的列索引）2,5,8,11,13,16,18
CONTINUOUS_CONFIG = {
    1: {},  # 第2列（保留连续处理）   
    4: {},   # 第5列（保留连续处理）
    7: {},  # 第8列（保留连续处理）
    10: {},  # 第11列（保留连续处理）
    12: {},  # 第13列（保留连续处理）
    15: {},  # 第16列（保留连续处理）
    17: {}   # 第18列（保留连续处理）
}



# In[22]:


# 模块三：辅助函数
def is_continuous(col_index):
    """判断是否为预定义的连续变量"""
    return col_index in CONTINUOUS_CONFIG.keys()

def generate_bins(series, col_index):
    """生成指定列的分箱结果"""
    config = CONTINUOUS_CONFIG.get(col_index, {})
    if 'bins' in config:
        return pd.cut(series, 
                     bins=config['bins'],
                     labels=config['labels'],
                     right=False)
    return series

def format_float(value):
    """数值显示格式化"""
    if value >= 1e6:
        return f'{value/1e6:.1f}M'
    elif value >= 1e3:
        return f'{value/1e3:.0f}K'
    return f'{value:.0f}'



# In[23]:


# 模块四：数据加载
def load_data(filepath):
    """数据加载与预处理"""
    df = pd.read_csv(filepath)
    print("实际数据列名:", df.columns.tolist())  # 新增列名打印
    # 记录原始元数据
    meta = {
        'shape': df.shape,
        'missing_codes': ['A65','A124', ],
        'continuous_cols': [df.columns[i] for i in CONTINUOUS_CONFIG.keys()]
    }
    return df, meta


# In[24]:


# 模块五：缺失值分析可视化
def plot_missing_analysis(df, missing_codes, save_path='缺失分析报告'):
    """综合缺失分析可视化"""
    # 缺失模式矩阵
    plt.figure(figsize=(12, 6))
    msno.matrix(df.replace(missing_codes, np.nan))
    plt.title('缺失值分布矩阵', fontsize=14)
    plt.savefig(f'{save_path}/1-缺失矩阵.png', dpi=300, bbox_inches='tight')
    plt.close()

    # 缺失相关性热力图
    plt.figure(figsize=(10, 8))
    msno.heatmap(df.replace(missing_codes, np.nan))
    plt.title('缺失值相关性', fontsize=14)
    plt.savefig(f'{save_path}/2-缺失热力.png', dpi=300, bbox_inches='tight')
    plt.close()

    # 分特征可视化
    n_cols = 4
    n_features = len(df.columns)
    plt.figure(figsize=(18, 6*(n_features//n_cols+1)))

    for idx, col in enumerate(df.columns):
        ax = plt.subplot(int(np.ceil(n_features/n_cols)), n_cols, idx+1)
        col_index = df.columns.get_loc(col)
        series = df[col].replace(missing_codes, np.nan)

        if is_continuous(col_index):
            # 连续变量分布
            sns.histplot(series.dropna(), kde=True, ax=ax, color=COLOR_NORMAL,
                        bins=20, edgecolor='white', linewidth=0.5)
            ax.set_title(f"连续变量: {col}\n缺失率: {series.isna().mean():.1%}")
            ax.yaxis.set_major_formatter(PercentFormatter(xmax=series.count()))
        else:
            # 离散变量分布
            counts = series.astype(str).value_counts(normalize=True)
            missing_rate = series.isna().mean()
            colors = [COLOR_MISSING if x in missing_codes else COLOR_NORMAL 
                     for x in counts.index]

            counts.plot.barh(ax=ax, color=colors)
            ax.set_title(f"离散变量: {col}\n缺失率: {missing_rate:.1%}")

        ax.grid(True, alpha=0.3)
        ax.set_xlabel('')

    plt.tight_layout()
    plt.savefig(f'{save_path}/3-特征分布.png', dpi=300, bbox_inches='tight')
    plt.close()


# In[25]:


# 模块六：数据预处理(缺失值填充)
def process_data(raw_df, missing_codes):
    """数据清洗与预处理流水线（含特殊缺失值处理）"""
    df = raw_df.copy()

    # === 阶段1：通用缺失值替换 ===
    df.replace(missing_codes, np.nan, inplace=True)
    # === 新增：标签列处理 ===
    if 'label' in df.columns:
        df['label'] = df['label'].replace(2, 0)
        print("标签列修改完成：2 -> 0")
    else:
        print("警告：未找到label列，跳过标签修改")
    # === 阶段2：特殊列缺失值处理 ===
    # 储蓄账户（第6列，索引5）填充A63
    if len(df.columns) > 5:
        savings_col = df.columns[5]
        if savings_col in df:
            # 添加A63到分类
            if pd.api.types.is_categorical_dtype(df[savings_col]):
                df[savings_col] = df[savings_col].cat.add_categories('A63')
            df[savings_col].fillna('A63', inplace=True)

    # 财产类型（第12列，索引11）填充A124
    if len(df.columns) > 11:
        property_col = df.columns[11]
        if property_col in df:
            # 添加A124到分类
            if pd.api.types.is_categorical_dtype(df[property_col]):
                df[property_col] = df[property_col].cat.add_categories('A124')
            df[property_col].fillna('A124', inplace=True)

    # === 阶段3：类型修正 ===
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[col] = pd.to_numeric(df[col], errors='coerce')

    # === 阶段4：通用缺失值填充 ===
    # 连续变量中位数填充
    numeric_cols = df.select_dtypes(include=np.number).columns
    for col in numeric_cols:
        median_val = df[col].median()
        df[col].fillna(median_val if not pd.isna(median_val) else 0, inplace=True)

    # 离散变量处理（排除已特殊处理的列）
    category_cols = df.select_dtypes(exclude=np.number).columns
    exclude_cols = []
    if len(df.columns) > 5: exclude_cols.append(df.columns[5])
    if len(df.columns) > 11: exclude_cols.append(df.columns[11])
    category_cols = category_cols.difference(exclude_cols)

    for col in category_cols:
        # 添加Missing分类
        if pd.api.types.is_categorical_dtype(df[col]):
            df[col] = df[col].cat.add_categories('Missing')
        df[col].fillna('Missing', inplace=True)

    # === 阶段5：特征分箱 ===
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[f'{col}_BINNED'] = generate_bins(df[col], col_index)

    return df


# In[26]:


#%% 模块七：处理结果验证
def plot_validation(raw_df, processed_df, col_name, save_path='处理结果'):
    """处理效果对比可视化"""
    plt.figure(figsize=(16, 6))
    col_index = raw_df.columns.get_loc(col_name)

    # 原始数据分布
    plt.subplot(1, 2, 1)
    raw_series = raw_df[col_name].replace(['A65','A101','A124','A143','A191'], np.nan)

    if is_continuous(col_index):
        sns.histplot(raw_series.dropna(), kde=True, color=COLOR_MISSING)
        plt.title(f'原始分布 - {col_name}')
    else:
        (raw_series.value_counts(normalize=True)
         .plot.pie(autopct='%1.1f%%', colors=[COLOR_MISSING, COLOR_NORMAL]))
        plt.ylabel('')

    # 处理结果分布
    plt.subplot(1, 2, 2)
    processed_series = processed_df[col_name]

    if is_continuous(col_index):
        sns.histplot(processed_series, kde=True, color=COLOR_NORMAL)
        plt.title(f'处理后分布 - {col_name}')
    else:
        (processed_series.value_counts(normalize=True)
         .plot.pie(autopct='%1.1f%%', colors=[COLOR_NORMAL, '#2ca02c']))
        plt.ylabel('')

    plt.suptitle(f'特征验证: {col_name}', y=1.05)
    plt.savefig(f'{save_path}/处理验证-{col_name}.png', dpi=300, bbox_inches='tight')
    plt.close()



# In[27]:


#%% 主执行流程
if __name__ == '__main__':
    # 数据加载
    raw_df, meta = load_data('original_data.csv')

    # 缺失分析
    plot_missing_analysis(raw_df, meta['missing_codes'])

    # 数据预处理
    processed_df = process_data(raw_df, meta['missing_codes'])

    # 结果验证
    for col in meta['continuous_cols']:
        plot_validation(raw_df, processed_df, col)

    # 保存处理结果
    processed_df.to_csv('processed_data.csv', index=False)
    print('数据处理流程完成！结果已保存至 processed_data.csv')


# In[31]:


# 模块六：数据预处理(缺失值填充)
def process_data(raw_df, missing_codes):
    """数据清洗与预处理流水线（含特殊缺失值处理）"""
    df = raw_df.copy()

    # === 阶段1：通用缺失值替换 ===
    df.replace(missing_codes, np.nan, inplace=True)

    # === 阶段2：特殊列缺失值处理 ===
    # 储蓄账户（第6列，索引5）填充A63
    if len(df.columns) > 5:
        savings_col = df.columns[5]
        if savings_col in df:
            # 添加A63到分类
            if pd.api.types.is_categorical_dtype(df[savings_col]):
                df[savings_col] = df[savings_col].cat.add_categories('A63')
            df[savings_col].fillna('A63', inplace=True)

    # 财产类型（第12列，索引11）填充A124
    if len(df.columns) > 11:
        property_col = df.columns[11]
        if property_col in df:
            # 添加A124到分类
            if pd.api.types.is_categorical_dtype(df[property_col]):
                df[property_col] = df[property_col].cat.add_categories('A124')
            df[property_col].fillna('A124', inplace=True)

    # === 阶段3：类型修正 ===
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[col] = pd.to_numeric(df[col], errors='coerce')

    # === 阶段4：通用缺失值填充 ===
    # 连续变量中位数填充
    numeric_cols = df.select_dtypes(include=np.number).columns
    for col in numeric_cols:
        median_val = df[col].median()
        df[col].fillna(median_val if not pd.isna(median_val) else 0, inplace=True)

    # 离散变量处理（排除已特殊处理的列）
    category_cols = df.select_dtypes(exclude=np.number).columns
    exclude_cols = []
    if len(df.columns) > 5: exclude_cols.append(df.columns[5])
    if len(df.columns) > 11: exclude_cols.append(df.columns[11])
    category_cols = category_cols.difference(exclude_cols)

    for col in category_cols:
        # 添加Missing分类
        if pd.api.types.is_categorical_dtype(df[col]):
            df[col] = df[col].cat.add_categories('Missing')
        df[col].fillna('Missing', inplace=True)

    # === 阶段5：特征分箱 ===
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[f'{col}_BINNED'] = generate_bins(df[col], col_index)

    return df


# In[32]:


# 模块六：数据预处理（missing）
def process_data(raw_df, missing_codes):
    """数据清洗与预处理流水线"""
    df = raw_df.copy()

    # 阶段1：缺失值替换
    df.replace(missing_codes, np.nan, inplace=True)

    # 阶段2：类型修正
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[col] = pd.to_numeric(df[col], errors='coerce')

    # 阶段3：缺失值填充
    # 连续变量用中位数填补
    numeric_cols = df.select_dtypes(include=np.number).columns
    for col in numeric_cols:
        median_val = df[col].median()
        df[col].fillna(median_val if not pd.isna(median_val) else 0, inplace=True)

    # 离散变量用'Missing'填补
    category_cols = df.select_dtypes(exclude=np.number).columns
    for col in category_cols:
        df[col].fillna('Missing', inplace=True)

    # 阶段4：特征分箱
    for col_index in CONTINUOUS_CONFIG:
        col = df.columns[col_index]
        df[f'{col}_BINNED'] = generate_bins(df[col], col_index)

    return df


# In[33]:


#%% 新增模块：缺失值处理对比可视化
import missingno as msno

def plot_missing_comparison(original_df, processed_df, save_path='缺失处理对比.png'):
    """
    生成缺失值处理前后对比矩阵
    :param original_df: 原始数据（未处理缺失）
    :param processed_df: 已处理数据
    :param save_path: 图片保存路径
    """
    plt.figure(figsize=(20, 10))

    # 处理前矩阵
    plt.subplot(1,2,1)
    msno.matrix(original_df.replace('Missing', np.nan))  # 还原离散变量的填充标记
    plt.title('处理前缺失分布', fontsize=14)

    # 处理后矩阵
    plt.subplot(1,2,2)
    msno.matrix(processed_df)
    plt.title('处理后缺失分布', fontsize=14)

    plt.tight_layout()
    plt.savefig(save_path, dpi=300, bbox_inches='tight')
    plt.close()
    print(f"对比图已保存至：{os.path.abspath(save_path)}")

#%% 新增模块：列名规范化函数
def normalize_cols(df):
    """统一处理列名格式：小写+下划线"""
    df.columns = (
        df.columns
        .str.strip()  # 去除前后空格
        .str.lower()  # 转为小写
        .str.replace(r'[\s\/]+', '_', regex=True)  # 替换空格和斜杠为下划线
    )
    return df

#%% 更新后的主流程
if __name__ == '__main__':
    # 加载原始数据（不处理缺失）
    raw_df = pd.read_csv('original_data.csv')
    raw_df = normalize_cols(raw_df)  # 现在可以正常调用

    # 处理数据
    processed_df = process_data(raw_df, missing_codes=['A65','A124'])

    # 生成对比图
    plot_missing_comparison(raw_df, processed_df)

    # 其他分析流程...

#%% 更新主流程
if __name__ == '__main__':
    # 加载原始数据（不处理缺失）
    raw_df = pd.read_csv('original_data.csv')
    raw_df = normalize_cols(raw_df)

    # 处理数据
    processed_df = process_data(raw_df, missing_codes=['A65','A124'])  # 替换为实际缺失编码

    # 生成对比图
    plot_missing_comparison(raw_df, processed_df)

    # 其他分析流程...


# In[38]:


#对离散特征进行独热编码(5.6新修改）
# -*- coding: utf-8 -*-
"""
离散特征独热编码处理
输入：processed_data_cleaned.csv
输出：encoded_data.csv
"""

#%% 模块一：依赖导入
import pandas as pd
import numpy as np
import os

#%% 模块二：编码函数
def onehot_encode(df, exclude_cols=['label']):
    """
    执行独热编码并生成编码报告
    :param df: 输入数据框
    :param exclude_cols: 需要排除的列（如目标变量）
    :return: 编码后的数据框，编码映射字典
    """
    # 识别离散特征（排除数值列和指定列）
    categorical_cols = df.select_dtypes(exclude=np.number).columns.tolist()
    categorical_cols = [col for col in categorical_cols 
                       if not col.endswith('_binned') and 
                       col not in exclude_cols]

    # 执行编码
    encoded_df = pd.get_dummies(df, columns=categorical_cols, 
                               prefix_sep=':', dummy_na=False)

    # 生成编码映射
    encoding_map = {}
    for col in categorical_cols:
        unique_values = df[col].unique()
        encoded_columns = [f"{col}:{val}" for val in sorted(unique_values)]
        encoding_map[col] = {
            'original_values': sorted(unique_values),
            'encoded_columns': encoded_columns
        }

    return encoded_df, encoding_map

#%% 模块三：编码报告生成
def save_encoding_report(encoding_map, report_path='encoding_report.txt'):
    """保存编码映射关系"""
    with open(report_path, 'w', encoding='utf-8') as f:
        for col, info in encoding_map.items():
            f.write(f"特征：{col}\n")
            f.write(f"原始值：{info['original_values']}\n")
            f.write(f"生成列：{info['encoded_columns'][:5]}...（共{len(info['encoded_columns'])}列）\n\n")
    print(f"编码报告已保存至：{os.path.abspath(report_path)}")

#%% 主流程
if __name__ == '__main__':
    # 加载处理后的数据
    processed_df = pd.read_csv('processed_data_cleaned.csv')

    # 执行编码
    encoded_df, encoding_map = onehot_encode(processed_df)

    # 保存结果
    encoded_df.to_csv('encoded_data.csv', index=False)
    save_encoding_report(encoding_map)

    print("编码后数据维度：", encoded_df.shape)
    print("首行示例：")
    print(encoded_df.head(1).T)


# In[ ]:




